import * as step from '@flow-step/step-toolkit'
import process from 'process'
import {getParams, IParams} from './params'
import path from 'path'
import * as fs from 'fs-extra'
import {setDependentFiles, checkGoVendor, readFile, checkGoVersion, generateLinterArgs} from './utils'
import {getOssRamCertificate} from './certificate'
import {rewriteIssuesJson} from './rewriteIssuesJson'

export const IS_LINUX = process.platform === 'linux'
export const IS_X64 = process.arch === 'x64'
export async function runStep(): Promise<void> {
    if (!IS_LINUX || !IS_X64) {
        throw new Error('GolangCodeScan only supports Linux/amd64 systems now')
    }

    await checkGoVersion()

    const params = getParams()
    step.info(`PIPELINE_ID=${params.pipelineID}`)
    step.info(`PIPELINE_NAME=${params.pipelineName}`)
    step.info(`BUILD_NUMBER=${params.buildNumber}`)
    step.info(`WORK_SPACE=${params.workSpace}`)
    step.info(`PROJECT_DIR=${params.projectDir}`)
    step.info(`BUILD_JOB_ID=${params.buildJobID}`)
    step.info(`STEP_ID=${params.stepIdentifier}`)
    step.info(`qualityGate=${params.redline}`)

    const dependencyDir = path.join(params.pluginDir,'golang-codescan')
    fs.mkdirSync(dependencyDir,{ recursive: true })
    await setDependentFiles(dependencyDir, params.golangciLintVersion)
    checkGoVendor(params)
    await scan(params,dependencyDir)
}



export async function scan(params: IParams,dependencyDir: string): Promise<void> {
    const copiedScanDir = path.join(dependencyDir,'code_origin')
    fs.mkdirSync(copiedScanDir,{ recursive: true })
    //use fs-extra to copy the entire dir
    fs.copySync(params.projectDir,copiedScanDir,{overwrite:true})

    const codeForDisplay = path.join(dependencyDir,'repocode') // do not change the dir name, html depends on it
    fs.mkdirSync(codeForDisplay,{ recursive: true })
    fs.copySync(params.projectDir,codeForDisplay,{overwrite:true})

    //exec diff
    let fileList = ""
    let exitCode:number
    if (!params.incrementalScan){
        step.info(`not use incrementalScan`)
    }else {
        step.info(`use incrementalScan`)
        const commitInfoPath = path.join(params.projectDir, '..', 'commitInfo')
        if (step.isDebug() && fs.existsSync(commitInfoPath)) {
            const commitInfoData = fs.readFileSync(commitInfoPath, 'utf-8')
            step.debug('commitInfo: ' + commitInfoData)
        }
        const fileListPath = path.join(dependencyDir, 'fileList.txt')
        const diffResultPath = path.join(dependencyDir, 'diff_result.json')
        step.info(`${path.join(dependencyDir, 'diff')} -c ${commitInfoPath} -s ${params.projectDir} -f go -o ${diffResultPath} -l ${fileListPath}`)
        exitCode = await step.exec.call(path.join(dependencyDir, 'diff'), ['-c', commitInfoPath, '-s', params.projectDir, '-f', 'go', '-o', diffResultPath, '-l', fileListPath], {
            listener: {
                stdoutLine: (line: string) => step.info(line),
                stderrLine: (line: string) => step.error(line)
            }
        })
        if (exitCode !== 0) {
            throw new Error(`diff exec failed, exit code: ${exitCode}`)
        }
        if (fs.existsSync(fileListPath)) {
            fileList = fs.readFileSync(fileListPath, 'utf-8')
        }
        step.debug(`fileList: ${fileList}`)
    }

    // exec golangci-lint
    const version = params.golangciLintVersion
    if (version === "1.63") {
        step.info('using golangci-lint version 1.63.4')
    } else {
        step.info(`using golangci-lint version ${version}`)
    }
    const linterArgs = generateLinterArgs(params.golangciLintVersion, fileList, params.exclusion)
    let commandResult = await step.exec.callOutput(path.join(dependencyDir, 'golangci-lint'), linterArgs, {
        cwd: params.projectDir,
    })
    if(commandResult.stderrContent.includes('level=error') || commandResult.stdoutContent === undefined){
        step.error(commandResult.stderrContent)
        step.debug(`stdout: `+ commandResult.stdoutContent)
        throw new Error(`golangci-lint exec failed, check the log above for more information`)
    }else{
        step.debug(`stdout: `+ commandResult.stdoutContent)
        step.debug(`stderr: `+commandResult.stderrContent)
    }
    const issuesOriginPath = path.join(dependencyDir, 'issues_origin.json')
    fs.writeFileSync(issuesOriginPath, commandResult.stdoutContent, 'utf-8')

    const issuesPath = path.join(dependencyDir, 'issues.json')
    await rewriteIssuesJson(issuesOriginPath, issuesPath);

    //get oss certificate
    const ossRamStsCertificate = await getOssRamCertificate()
    let endpoint = await step.oss.getEndpoint(process.env['ENDPOINT'] as string)
    if (!endpoint.startsWith('http://') && !endpoint.startsWith('https://')) {
        endpoint = 'https://' + endpoint;
    }
    step.info(`endpoint: ${endpoint}`)

    //exec builder
    exitCode = await step.exec.call('./builder',[
        '-m','report-template.html',
        '-d','issues.json',
        '-o','index.html',
        '-c',path.basename(codeForDisplay),
        '--diff','diff_result.json',
        '--region',ossRamStsCertificate.ossPathPrefix,
        '--pipeline',params.pipelineID.toString(),
        '--step',params.stepIdentifier,
        '--accessKeyId',ossRamStsCertificate.accessKeyId,
        '--accessKeySecret',ossRamStsCertificate.accessKeySecret,
        '--token',ossRamStsCertificate.securityToken,
        '--bucket',ossRamStsCertificate.bucketName,
        '--endpoint',endpoint,
        '--redlines',params.redline
    ],{
        listener: {
            stdoutLine: (line: string) => {
                step.addOutput(line);
                step.info(`${line}`);
            },
            stderrLine: (line: string) => step.error(line),
        },
        cwd: dependencyDir
    })
    if (exitCode !== 0) {
        throw new Error(`builder exec failed, exit code: ${exitCode}`)
    }
    step.addOutput(`[${process.env['stepIdentifier']}]STAT_INFO_TITLE:${process.env['name']}`)
}

runStep()
    .then(function() {
        step.success('run step successfully!')
    })
    .catch(function(err: Error) {
        step.error(err.message)
        process.exit(-1)
    })
